import base64
import json
import logging
import os
import random
import time

import cv2
import pyautogui
import requests
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from selenium import webdriver


def image_process(img_path: str, output_path: str) -> int:
    """
    Process captcha, replace background and lines to write.
    This function will return the number of black pixels,
    the idea is, when there are too many disturbing lines,
    it would be better to get a new free captcha.

    :param img_path: raw captcha path
    :param output_path: captcha output path
    :return: black pixels
    """
    image = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    blacks = 0
    for row in range(len(image)):
        for col in range(len(image[row])):
            cur_value = image[row][col]
            if cur_value < 30:
                image[row][col] = 255
                blacks += 1
                continue
            if cur_value > 200:
                image[row][col] = 255
            else:
                image[row][col] = 0
    cv2.imencode(".jpg", image)[1].tofile(output_path)
    return blacks


def get_access_token(api_key: str, secret_key: str) -> str:
    """
    Get the access token for the Baidu OCR.

    :param api_key: api key
    :param secret_key: secret key
    :return: access token
    """
    host = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&"
    host += f"client_id={api_key}&client_secret={secret_key}"
    response = requests.get(host)
    if response:
        return response.json()["access_token"]
    return None


def baidu_ocr(img_path: str, access_token: str) -> str:
    """
    Recognize captcha using baidu ocr.

    :param img_path: the location of processed captch image
    :param access_token: access token from get_access_token()
    :return: captcha string
    """
    url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"
    with open(img_path, "rb") as f:
        img = base64.b64encode(f.read())
    params = {"image": img}
    request_url = url + "?access_token=" + access_token
    headers = {"content-type": "application/x-www-form-urlencoded"}
    response = requests.post(request_url, data=params, headers=headers)
    if response:
        captcha = ""
        result = response.json()
        for word in result["words_result"]:
            captcha += word["words"]
        captcha = captcha.replace(" ", "")
        captcha = captcha.replace(".", "")
        return captcha
    return ""


def refresh_captcha(output_path: str, session_id: str) -> None:
    """
    Get a new captcha.

    :param output_path: captcha output path
    :param session_id: the session ID
    """
    url = f"https://cas.sysu.edu.cn/cas/captcha.jsp?time{str(random.random())}"
    headers = {"cookie": f"JSESSIONID={session_id}"}
    r = requests.get(url, headers=headers)
    with open(output_path, "wb") as f:
        f.write(r.content)


def email_notification(smtp_info: dict, errors: int) -> bool:
    """
    Send logger information to assigned email address.

    :param smtp_info: a dictionary of email login info.
    :param errors: the number of errors.
    :return: whether the email is sent successfully.
    """

    with open("amb.log", "r", encoding="utf-8") as f:
        txt = f.read()

    message = MIMEText(txt, "plain", "utf-8")
    message["From"] = Header("AMBot", "utf-8")
    message["To"] = Header("DearUser", "utf-8")
    message["Subject"] = Header(f"AMB finished with {errors} error(s)", "utf-8")

    try:
        smtpObj = smtplib.SMTP()
        smtpObj.connect(smtp_info["server"], smtp_info["port"])
        smtpObj.login(smtp_info["sender"], smtp_info["password"])
        smtpObj.sendmail(
            smtp_info["sender"], smtp_info["receivers"], message.as_string()
        )
        return True
    except smtplib.SMTPException:
        return False


if __name__ == "__main__":
    wd = os.path.dirname(os.path.abspath(__file__))
    os.chdir(wd)

    logger = logging.getLogger("test")
    logger.setLevel(level=logging.DEBUG)

    formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")

    file_handler = logging.FileHandler("amb.log", encoding="utf-8", mode="w")
    file_handler.setLevel(level=logging.INFO)
    file_handler.setFormatter(formatter)

    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(logging.DEBUG)
    stream_handler.setFormatter(formatter)

    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)

    with open("config.json") as f:
        info = json.load(f)

    access_token = get_access_token(
        info["baidu_ocr"]["api_key"], info["baidu_ocr"]["secret_key"]
    )

    errors = 0
    for user in info["users"]:
        logger.info(f"Start processing for user {user['netid']}")
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--disable-blink-features=AutomationControlled")
        driver = webdriver.Chrome(chrome_options=chrome_options)
        driver.execute_cdp_cmd(
            "Page.addScriptToEvaluateOnNewDocument",
            {
                "source": """
                    Object.defineProperty(navigator, 'webdriver', {
                    get: () => false
                    })
                """
            },
        )
        driver.get(
            "https://cas.sysu.edu.cn/cas/login?service=https://portal.sysu.edu.cn/shiro-cas"
        )
        attampts = 10
        while True:
            attampts -= 1
            driver.find_element_by_xpath('//*[@id="username"]').send_keys(user["netid"])
            driver.find_element_by_xpath('//*[@id="password"]').send_keys(user["pwd"])
            session_id = driver.get_cookies()[0]["value"]
            while True:
                refresh_captcha("./img_captcha_raw.jpg", session_id)
                blacks = image_process(
                    "./img_captcha_raw.jpg", "./img_captcha_proc.jpg"
                )
                if blacks > 30:
                    continue
                ca = baidu_ocr("./img_captcha_proc.jpg", access_token)
                if len(ca) != 4:
                    continue
                break
            driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(ca)
            driver.find_element_by_xpath('//*[@id="fm1"]/section[2]/input[4]').click()
            if not driver.find_elements_by_xpath('//*[@id="fm1"]/div[1]/span'):
                logger.info(f"Login succeeded.")
                break
            if attampts:
                logger.info("Wrong captcha, retry.")
                time.sleep(2)
                continue
            logger.info("Wrong captcha, exit.")
            errors += 1
            driver.close()
            driver.quit()
            exit()
        driver.get("http://jksb.sysu.edu.cn/infoplus/form/XNYQSB/start")
        time.sleep(3)
        button = pyautogui.locateCenterOnScreen("./next_step.png")
        if button:
            pyautogui.click(button)
            time.sleep(3)
            pyautogui.click(button)
            time.sleep(2)
            driver.refresh()
            time.sleep(2)
            results = driver.find_elements_by_xpath('//*[@id="title_content"]/nobr')
            if results:
                result_text = results[0].text
                logger.info(f"Result: {result_text}")
                if "已完成" in result_text:
                    logger.info(f"{user['netid']} finished.")
                else:
                    logger.warning(f"{user['netid']} status unknown.")
                    errors += 1
        else:
            logger.error("Cannot find the next step button, exit.")
            logger.error(f"{user['netid']} failed.")
            errors += 1
        driver.close()
        driver.quit()

    logger.info(f"Program finished with {errors} error(s).")
    if info["send_email"] == 2 or (info["send_email"] == 1 and errors):
        if email_notification(info["smtp"], errors):
            logger.info("email sent successfully.")
        else:
            logger.error("Failed to send email.")
